unit Unit2;

interface

procedure ReviseShiftsAndEncashments ( span: integer );

implementation

uses
   System.SysUtils, System.DateUtils, System.Types, System.Classes, System.Generics.Collections, System.Generics.Defaults, System.Math, Unit4;

type
   {Запись из файлика}

   TReviseType   =       ( ReviseGood, ReviseEKM, ReviseKKM );

   TPaymentType  =       ( NoPayment , CASH , BANKCARD );

   TPaperValue   =       ( p10, p50, p100, p200, p500, p1000, p2000, p5000 );

   TPapers       = array [ TPaperValue ] of integer; {количество купюр различных номиналов}

   TPaperAmounts = array [ TPaperValue ] of Int64 {купюры в копейках};

   TFileRecord = record
      actId,
      trId,
      comment
         : string;
      shId,
      amount
         : Int64;
      shFromDt,
      shToDt,
      actDt
         : TDateTime;
      atId,
      accId,
      paperCount
         : Integer;
      paperValue
         : TPaperValue;
      ptId
         : TPaymentType;
   end;

   {Фискальные операции терминала}
   TTransaction = record
      papers,        {количество купюр различных номиналов в платежей}
      previousPapers {для инкассаций: количество купюр различных номиналов с предыдущей инкассации}
         : TPapers;

      actId,
      trId,
      comment
         : string;
      shId,
      amount,
      previousAmount
         : Int64;
      Dt
         : TDateTime;
      atId,
      accId
         : Byte;
      ptId
         : TPaymentType;
      isGood
         : Boolean;
   end;

   {Кассовые смены терминала}
   TShift = record
      Movements,
      Payments,
      Returns,
      Corrections
         : array [ 3 .. 6 , TPaymentType ] of Int64;
      OpenSum,
      CloseSum,
      ekm, {разность сумм в кассе}
      nal  {сумма наличных платежей, возвратов, движений средств и коррекций}
         : array [ 3 .. 6 ] of Int64;
      comment
         : string;
      KKMopenSum,
      KKMcloseSum,
      shId,
      kkm, {разность нарастающих итогов}
      pay, {сумма всех платежей и возвратов}
      zero {движения средств по карте и чеки коррекции по карте}
         : Int64;
      FromDt,
      ToDt
         : TDateTime;
      isGood
         : Boolean;
   end;

Const
   ActionType : array [ 1 .. 23 ] of string = (
      'Платёж',
      'Отмена/Возврат платежа',
      'ККМ Нарастающий итог на начало смены',
      'ККМ Нарастающий итог на конец смены',
      'ЕКМ Сумма на начало смены',
      'ЕКМ Сумма на конец смены',
      'Внесение денег в кассу',
      'Изъятие денег из кассы',
      'Инкассация',
      'Операция в процессе создания, создания платежа',
      'Операция в процессе создания, отмена платежа',
      'Операция в процессе создания, возврат аванса',
      'Возврат аванса',
      'Операция отклонена, возврат аванса',
      'Внесение денег в кассу, в процессе',
      'Изъятие денег из кассы, в процессе',
      'Инкассация, в процессе',
      'Принятые платежи через сторонний АРМ',
      'Внесение денег через сторонний АРМ',
      'Чек коррекции прихода',
      'Чек коррекции расхода',
      'Печать чека коррекции прихода, в процессе',
      'Печать чека коррекции расхода, в процессе'
   );
   AccountType : array [ 0..6 ] of string = (
      'Общий кассовый',
      'ошибка',
      'ошибка',
      'Собственные(407)',
      'Третьих лиц(408)',
      'Связьбанк(408)',
      'Финсервисы,БПА(408)'
   );
   pl = Low  ( TPaperValue );
   ph = High ( TPaperValue );

Var
   StringList,                              {Прочитанный файл как совокупность строк}
   ParsingList : TStringList;               {Строки файла, которые не удалось разобрать}
   FileRecords : TList <TFileRecord>;       {Разобранные записи action.csv}
   Transactions: TList <TTransaction>;      {Фискальные операции}
   Shifts      : TList <TShift>;            {Смены}
   FileRecordsTimeComparer : IComparer<TFileRecord>;
   TransactionsTimeComparer : IComparer<TTransaction>;
   ShiftsTimeComparer : IComparer<TShift>;
   PaymentType : array [ TPaymentType ] of string;
   PaperAmounts: TPaperAmounts;

function middle ( s: string ): string;
begin
   if Length ( s ) > 2
   then middle := s.Substring( 1, Length( s) -2)
   else middle := '';
end;

procedure clearFileRecord ( var a : TFileRecord );
begin
   with a do  begin
      comment := '';
      shId := 0;
      actId := '';
      atId := 0;
      amount := 0;
      trId := '';
      ptId := NoPayment;
      accId := 0;
      shFromDt := 0;
      shToDt := 0;
      actDt := 0;
      paperCount := 0;
      paperValue := pl;
   end;
end;

procedure clearShift ( var a : TShift );
var
   acc: byte;
   p: TPaymentType;
begin
   with a do  begin
      comment := '';
      isGood := True;
      shId := 0;
      KKMopenSum := 0;
      KKMcloseSum := 0;
      FromDt := 0;
      ToDt := 0;
      for acc := 3 to 6 do  begin
         OpenSum             [ acc ] := 0;
         CloseSum            [ acc ] := 0;
         for p := Low (TPaymentType) to High ( TPaymentType) do  begin
            Movements       [ acc , p ] := 0;
            Payments        [ acc , p ] := 0;
            Returns         [ acc , p ] := 0;
            Corrections     [ acc , p ] := 0;
         end;
      end;
   end;
end;

procedure clearTransaction ( var a : TTransaction );
var p: TPaperValue;
begin
   with a do  begin
      isGood := True;
      for p := pl to ph do  begin
         previousPapers [ p ] := 0;
                 papers [ p ] := 0;
      end;
      shId := 0;
      actId := '';
      trId := '';
      atId := 0;
      amount := 0;
      ptId := NOPayment;
      accId := 0;
      Dt := 0;
      comment := '';
   end;
end;

{Разбирает одну строку из StringList и складывает её в FileRecords или в ParsingList}
procedure ReadString ( i: integer );
var
   wordie: TArray<string>; {Слова строки}
   f: TFileRecord;         {Разобранная строка файла}
   p: TPaperValue;
begin
   wordie := StringList [ i ].Split([';']);
   ClearFileRecord ( f );
   try
      with f do  begin
         if middle ( wordie [ 0 ] ) = ''
         then shId := 0
         else shId := middle ( wordie [ 0 ] ).ToInteger;
         if middle ( wordie [ 1 ] ) = ''
         then shFromDt := 0
         else shFromDt   := StrToDateTime ( middle ( wordie [ 1 ] ) );
         if middle ( wordie [ 2 ] ) = ''
         then shToDt := 0
         else shToDt     := StrToDateTime ( middle ( wordie [ 2 ] ) );
         actId      :=                 middle ( wordie [ 3 ] );
         if middle ( wordie [ 4 ] ) = ''
         then actDt := 0
         else actDt      := StrToDateTime ( middle ( wordie [ 4 ] ) );
         if middle ( wordie [ 5 ] ) = ''
         then atId := 0
         else atId := middle ( wordie [ 5 ] ).ToInteger;
         if middle ( wordie [ 6 ] ) = ''
         then amount := 0
         else amount := middle ( wordie [ 6 ] ).ToInteger;
         comment    :=                 middle ( wordie [ 7 ] );
         trId       :=                 middle ( wordie [ 8 ] );
         if middle ( wordie [ 10 ] ) = ''
         then accId := 0
         else accId := middle ( wordie [ 10 ] ).ToInteger;
         if middle ( wordie [ 12 ] ) = ''
         then paperCount := 0
         else paperCount := middle ( wordie [ 12 ] ).ToInteger;
         if middle ( wordie [ 9 ] ) = ''
         then ptId := NoPayment
         else
            case middle ( wordie [ 9 ] ).ToInteger of
               1:   ptId := BANKCARD;
               7:   ptId := CASH;
               else ptId := NoPayment;
            end;
         if middle ( wordie [ 11 ] ) = ''
         then paperCount := 0
         else
            for p := pl to ph do
               if middle ( wordie [ 11 ] ).ToInteger = PaperAmounts [ p ]
               then begin
                  paperValue := p;
                  Break;
               end;
         if ( accId <> 0 ) or ( atId = 3 ) or ( atId = 4 ) then FileRecords.Add( f );{сумма на конец смены ЕКМ учитывается только по счетам учёта (407-408)}
      end;
   except
      ParsingList.Add( StringList [ i ] );
   end;
end;

{Формирует список транзакций и список смен}
procedure ReadAction ( filename: string );
var
   Transaction: TTransaction;
   PreviousPapers: TPapers;
   Shift: TShift;
   p: TPaperValue;
   i, i0, r, s, t: integer;
   PreviousAmount: int64;
   foundShift, foundTransaction: Boolean;
   acc: Byte;
begin

   {разбираем файл}
//   writeLn('   разбираю файлик');
   ParsingList.Clear;
   StringList.LoadFromFile( filename );
   for i := 0 to StringList.Count - 1 do  ReadString ( i );
   StringList.Clear;
   if ParsingList.Count > 0 then ParsingList.SaveToFile('shift parsing errors.txt');
   ParsingList.Clear;
   FileRecords.Sort ( FileRecordsTimeComparer );

   {заполняем список смен}
//   writeLn('   составляю список смен');
   for r := 0 to FileRecords.Count - 1 do  begin
      foundShift := False;
      for s := 0 to Shifts.Count - 1 do
         if FileRecords [ r ].shId = Shifts [ s ].shId
         then begin
            foundShift := True;
            Break;
         end;
      if not foundShift
      then begin
         clearShift( Shift );
         Shift.shId   := FileRecords [ r ].shId;
         Shift.FromDt := FileRecords [ r ].shFromDt;
         Shift.ToDt   := FileRecords [ r ].shToDt;
         if  Shift.ToDt <> 0
         then Shifts.Add( Shift ); {не учитываем открытую смену}
      end;
   end;
   Shifts.Sort ( ShiftsTimeComparer );

   {заполняем список транзакций}
//   write('   составляю список транзакций по купюрам ');
   i0 := 0;
   for r := 0 to FileRecords.Count - 1 do  begin
{      if r mod 1000 = 0
      then write ( ' ',Round(r/1000),'k/',Round(FileRecords.Count/1000),'k');}
      foundTransaction := False;

      for t := i0 to Transactions.Count - 1 do
         if FileRecords [ r ].shId = Transactions [ t ].shId
         then
            if FileRecords [ r ].actId = Transactions [ t ].actId
            then begin
               i0 := t;
               foundTransaction := True;
               Transaction := Transactions [ t ];
               Transaction.papers [ FileRecords [ r ].paperValue ] :=
               Transaction.papers [ FileRecords [ r ].paperValue ] +
                                    FileRecords [ r ].paperCount;
               Transactions [ t ] := Transaction;
               Break;
            end;

      if ( not foundTransaction ) and ( FileRecords [ r ].shToDt <> 0 ) {не учитываем открытую смену}
      then begin
         clearTransaction    (  Transaction                  );
         Transaction.papers  [  FileRecords [ r ].paperValue ] := FileRecords [ r ].paperCount;
         Transaction.comment := FileRecords [ r ].comment;
         Transaction.shId    := FileRecords [ r ].shId;
         Transaction.actId   := FileRecords [ r ].actId;
         Transaction.trId    := FileRecords [ r ].trId;
         Transaction.amount  := FileRecords [ r ].amount;
         Transaction.Dt      := FileRecords [ r ].actDt;
         Transaction.atId    := FileRecords [ r ].atId;
         Transaction.ptId    := FileRecords [ r ].ptId;
         Transaction.accId   := FileRecords [ r ].accId;
         Transactions.Add    (  Transaction     );
      end;

   end;
//   writeLn;
   FileRecords.Clear;
   Transactions.Sort ( TransactionsTimeComparer );

   {рассчитываем транзакции}
//   writeLn('   рассчитываю суммы платежей между инкассациями');
   PreviousAmount := 0;
   for p := pl to ph do  PreviousPapers [ p ] := 0;
   for t := 0 to Transactions.Count - 1 do
      if Transactions [ t ].ptId = CASH
      then
         case Transactions [ t ].atId of
            1:
               begin
                  for  p := pl to ph do
                     PreviousPapers [ p ] := PreviousPapers [ p ] + Transactions [ t ].papers [ p ];
                  PreviousAmount := PreviousAmount + Transactions [ t ].amount;
               end;
            2:
               PreviousAmount := PreviousAmount - Transactions [ t ].amount;
            9:
               begin
                  Transaction := Transactions [ t ];
                  for p := pl to ph do  begin
                     Transaction.isGood :=
                        Transaction.isGood
                        and ( Transaction.papers [ p ] = PreviousPapers [ p ] );
                     Transaction.previousPapers [ p ] := PreviousPapers [ p ];
                     PreviousPapers [ p ] := 0;
                  end;
                  Transaction.isGood :=
                     Transaction.isGood
                     and ( Transaction.amount = PreviousAmount );
                  Transaction.previousAmount := PreviousAmount;
                  PreviousAmount := 0;
                  Transactions [ t ] := Transaction;
               end;
         end;

   {рассчитываем смены}
//   writeLn('   рассчитываю остатки смен');
   for t := 0 to Transactions.Count - 1 do

      for s := 0 to Shifts.Count - 1 do

         if Transactions [ t ].shId = Shifts [ s ].shId
         then begin

            Shift := Shifts [ s ];

            case Transactions [ t ].atId of
               1, 18: Shift.Payments [ Transactions [ t ].accId , Transactions   [ t ].ptId ] := Shift.Payments [ Transactions [ t ].accId , Transactions   [ t ].ptId ] + Transactions [ t ].amount;
               2, 13: Shift.Returns [ Transactions [ t ].accId , Transactions   [ t ].ptId ] := Shift.Returns [ Transactions [ t ].accId , Transactions   [ t ].ptId ] - Transactions [ t ].amount;
               3:     Shift.KKMopenSum  := Shift.KKMopenSum  + Transactions [ t ].amount;
               4:     Shift.KKMcloseSum := Shift.KKMcloseSum + Transactions [ t ].amount;
               5:     Shift.OpenSum  [ Transactions [ t ].accId ] := Shift.OpenSum  [ Transactions [ t ].accId ] + Transactions [ t ].amount;
               6:     Shift.CloseSum [ Transactions [ t ].accId ] := Shift.CloseSum [ Transactions [ t ].accId ] + Transactions [ t ].amount;
               7, 19: Shift.Movements [ Transactions [ t ].accId , Transactions   [ t ].ptId ] := Shift.Movements [ Transactions [ t ].accId , Transactions   [ t ].ptId ] + Transactions [ t ].amount;
               8,  9: Shift.Movements [ Transactions [ t ].accId , Transactions   [ t ].ptId ] := Shift.Movements [ Transactions [ t ].accId , Transactions   [ t ].ptId ] - Transactions [ t ].amount;
               20:    Shift.Corrections [ Transactions [ t ].accId , Transactions   [ t ].ptId ] := Shift.Corrections [ Transactions [ t ].accId , Transactions   [ t ].ptId ] + Transactions [ t ].amount;
               21:    Shift.Corrections [ Transactions [ t ].accId , Transactions   [ t ].ptId ] := Shift.Corrections [ Transactions [ t ].accId , Transactions   [ t ].ptId ] - Transactions [ t ].amount;
            end; {case}

            Shifts [ s ] := Shift;
            Break;

         end;{transactions[t].shId=shifts[s].shId}
   for i := 0 to Shifts.Count - 1 do  begin
      Shift := Shifts [ i ];
      with Shift do  begin
         kkm := KKMcloseSum - KKMopenSum;
         pay := 0;
         zero := 0;
         for acc := 3 to 6 do  begin
            pay := pay + Payments [ acc , CASH ] + Payments [ acc , BANKCARD ];
            zero := zero + Movements [ acc , BANKCARD ] + Corrections [ acc , BANKCARD ];
            ekm [ acc ] := CloseSum [ acc ] - OpenSum [ acc ];
            nal [ acc ] := Payments [ acc , CASH ] + Returns [ acc , CASH ] + Movements [ acc , CASH ] + Corrections [ acc , CASH ];
         end;
      end;
      Shifts [ i ] := Shift;
   end;{рассчитываем смены}

end;{ReadAction}

{Записывает одну транзакцию}
function WriteOneCashTransaction ( span, i: Integer;  a, b: int64;  FirstLine: Boolean;  r: TReviseType ): Boolean;
var
   Transaction: TTransaction;
   c: string;
   p: TPaperValue;
begin
   if DaysBetween ( Today , Transactions [ i ].Dt ) > span
   then
      WriteOneCashTransaction := FirstLine
   else begin

      if FirstLine
      then begin

         c := ';;;;;;;;;;;ко;л-;во;куп;юр;тра;нза;кции;ко;л-;во;куп;юр;пред;плат;ежей';
         StringList.Add( c );

         c :=      'Смена';
         c := c + ';Операция';
         c := c + ';Способ оплаты';
         c := c + ';Счёт учёта';
         c := c + ';Время (UTC 0)';
         c := c + ';Чек';
         c := c + ';Платёж';
         c := c + ';Сумма операции';
         c := c + ';Сумма по купюрам';
         c := c + ';Сумма по платежам';
         c := c + ';Сумма по купюрам платежей';
         for p := pl to ph do  c := c + ';'+Round ( PaperAmounts [ p ] / 100 ).ToString;
         for p := pl to ph do  c := c + ';'+Round ( PaperAmounts [ p ] / 100 ).ToString;
         c := c + ';Комментарий';
         StringList.Add( c );

      end;

      if r <> ReviseGood
      then begin
         Transaction := Transactions [ i ];
         Transaction.isGood := False;
         Transactions [ i ] := Transaction;
      end;

      with Transactions [ i ] do  begin
         c :=         shid.ToString;
         c := c + ';'+ActionType [ atId ];
         c := c + ';'+PaymentType [ ptId ];
         c := c + ';'+AccountType [ accId ];
         c := c + ';'+DateTimeToStr ( Dt );
         c := c + ';'''+actid;
         c := c + ';'+trid;
         c := c + ';'+FormatFloat ( '#########.00' ,amount/100);
         c := c + ';'+FormatFloat ( '#########.00' ,a/100);
         if atId = 9
         then c := c + ';'+FormatFloat ( '#########.00', previousAmount/100)
         else c := c + ';';
         if atId = 9
         then c := c + ';'+FormatFloat ( '#########.00', b/100)
         else c := c + ';';
         for p := pl to ph do  c := c + ';'+papers [ p ].ToString;
         for p := pl to ph do
            if atId = 9
            then c := c + ';'+previousPapers [ p ].ToString
            else c := c + ';';
         c := c + ';'+comment;
         StringList.Add( c );
      end;

      WriteOneCashTransaction := False;
   end;
end;



{ 3. Сверка итогов:
   разность нарастающих итогов ККМ = сумма всех платежей
   разность итогов ЕКМ = сумма наличных платежей и движений средств
   сумма ЕКМ на начало смены = сумма ЕКМ на конец предыдущей смены
   сумма ККМ на начало смены = сумма ККМ на конец предыдущей смены
   + хорошие смены пропускаем
   - плохие смены выводим на экран}
procedure ReviseShifts ( span: integer;   r: TReviseType;   filename: string );
var
   Shift: TShift;
   c: string;
   i, acc: integer;
   allGood: Boolean;
   pt: TPaymentType;
begin
//   writeLn('   сравниваю итоги смен с операциями в сменах');
   for i := 0 to Shifts.Count - 1 do  begin
      Shift := Shifts [ i ];
      with Shift do  begin
         isGood := True;

         if ( DaysBetween ( Today , FromDt ) > span ) {так как не требуется сверять данные из разных систем}
         or ( DaysBetween ( Today , ToDt   ) > span ) {то мы можем сразу не учитывать старые платежи}
         then begin
            Shifts [ i ] := Shift;
            Continue;
         end;

         for acc := 3 to 6 do
            if ( ekm [ acc ] <> nal [ acc ] ) and ( r <> ReviseGood )
            then begin
               isGood := False;
               comment := comment + 'Сумма в кассе ЕКМ (' + AccountType [ acc ] + ') не равна сумме наличных операций.   ';
            end
            else
               if ( ekm [ acc ] = nal [ acc ] ) and ( r = ReviseGood )
               then isGood := False;

         if r = reviseKKM
         then
            if kkm <> pay
            then begin
               isGood := False;
               comment := comment + 'Сумма нарастающих итогов не равна сумме всех платежей (возможно, допроведение платежей или печать чеков).   ';
            end;

         if ( zero <> 0 ) and ( r <> ReviseGood )
         then begin
            isGood := False;
            comment := comment + 'Обнаружены движения средств или чеки коррекции не наличными.   ';
         end
         else
            if ( zero = 0 ) and ( r = ReviseGood )
            then isGood := False;

         if i > 0
         then begin

            for acc := 3 to 6 do
               if ( OpenSum [ acc ] <> Shifts [ i - 1 ].CloseSum [ acc ] ) and ( r <> ReviseGood )
               then begin
                  isGood := False;
                  comment := comment + 'Сумма на начало смены ЕКМ (' + AccountType [ acc ] + ') не равна сумме на конец предыдущей смены ЕКМ.   ';
               end
               else
                  if ( OpenSum [ acc ] = Shifts [ i - 1 ].CloseSum [ acc ] ) and ( r = ReviseGood )
                  then isGood := False;

            if r = reviseKKM
            then
               if KKMopenSum <> Shifts [ i - 1 ].KKMcloseSum
               then begin
                  isGood := False;
                  comment := comment + 'Нарастающий итог на начало смены не равен нарастающему итогу на конец предыдущей смены (возможно, печать чеков).   ';
               end;

         end;
      end;
      Shifts [ i ] := Shift;
   end;{сравниваем итоги смен с операциями в сменах}

   allGood := True;
   StringList.Clear;
//   writeLn('   записываю заголовок списка смен');
   for i := 0 to Shifts.Count - 1 do
      allGood := allGood and Shifts [ i ].isGood;
   if not allGood
   then begin
      c :=      'Смена';
      c := c + ';Время открытия, UTC 0';
      c := c + ';Время закрытия, UTC 0';
      c := c + ';Описание ошибки';

      c := c + ';';
      c := c + ';ЕКМ разность сумм в кассе';
      c := c + ';ЕКМ сумма наличных операций';
      c := c + ';';

      if r = reviseKKM then begin
         c := c + ';ККМ разность нарастающих итогов';
         c := c + ';ЕКМ сумма платежей смены';
         c := c + ';';
      end;

      c := c + ';Нарастающий итог на начало смены';
      c := c + ';Нарастающий итог на конец смены';
      for acc := 3 to 6 do  begin
         c := c + ';Сумма на начало смены, ' + AccountType [ acc ];
         c := c + ';Сумма на конец смены, ' + AccountType [ acc ];
         for pt := CASH to BANKCARD do  begin
            c := c + ';Платежи ' + PaymentType [ pt ] + ' на ' + AccountType [ acc ];
            c := c + ';Возвраты ' + PaymentType [ pt ] + ' на ' + AccountType [ acc ];
            if pt = CASH then c := c + ';Движения средств ' + PaymentType [ pt ] + ' на ' + AccountType [ acc ];
            if pt = CASH then c := c + ';Чеки коррекции ' + PaymentType [ pt ] + ' на ' + AccountType [ acc ];
         end;
      end;
      StringList.Add(c);
   end;

//   writeLn('   записываю список смен');
   for i := 0 to Shifts.Count - 1 do
      with Shifts [ i ] do
         if not isGood
         then begin
            c :=         shId.ToString;
            c := c + ';'+DateTimeToStr(FromDt);
            c := c + ';'+DateTimeToStr(ToDt);
            c := c + ';'+comment;

            c := c + ';';
            c := c + ';'+FormatFloat ( '#########.00' , (ekm[3]+ekm[4]+ekm[5]+ekm[6])/100);
            c := c + ';'+FormatFloat ( '#########.00' , (nal[3]+nal[4]+nal[5]+nal[6])/100);
            c := c + ';';

            if r = reviseKKM then begin
               c := c + ';'+FormatFloat ( '#########.00', kkm / 100 );
               c := c + ';'+FormatFloat ( '#########.00', pay / 100 );
               c := c + ';';
            end;

            c := c + ';'+FormatFloat ( '#########.00' , KKMopenSum / 100 );
            c := c + ';'+FormatFloat ( '#########.00' , KKMcloseSum / 100 );
            for acc := 3 to 6 do  begin
               c := c + ';'+ FormatFloat ( '#########.00' , OpenSum [ acc ] / 100 );
               c := c + ';'+ FormatFloat ( '#########.00' , CloseSum [ acc ] / 100 );
               for pt := CASH to BANKCARD do  begin
                  c := c + ';'+ FormatFloat ( '#########.00' , Payments [ acc , pt ] / 100 );
                  c := c + ';'+ FormatFloat ( '#########.00' , Returns [ acc , pt ] / 100 );
                  if pt = CASH then c := c + ';'+ FormatFloat ( '#########.00' , Movements [ acc , pt ] / 100 );
                  if pt = CASH then c := c + ';'+ FormatFloat ( '#########.00' , Corrections [ acc , pt ] / 100 );
               end;
            end;
            StringList.Add( c );
         end;
   if StringList.Count > 0 then StringList.SaveToFile( filename );
   StringList.Clear;
end;

{ 2. Сверка инкассаций:
   для инкассаций дополнительно сравниванием сумму купюр инкассации с суммой купюр предшествующих платежей
   + список хороших инкассаций
   - список плохих операций

  1. Сверка наличных операций:
   для каждой наличной операции сравниваем сумму операции и сумму купюр
 + хорошие операции пропускаем
 - плохие операции выводим на экран}
procedure ReviseCashTransactions ( span: integer;  r: TReviseType;  filename: string );
var
   i: integer;
   p: TPaperValue;
   a, b: int64;
   FirstLine: Boolean;
begin
   FirstLine := True;
   StringList.Clear;
//   writeLn('   записываю список операций');
   for i := 0 to Transactions.Count - 1 do
      with Transactions [ i ] do
         if ( ( atId = 1 ) or ( atId = 9 ) ) and ( ptId = CASH )
         then begin
            a := 0;
            b := 0;
            for p := pl to ph do  begin
               a := a + papers [ p ] * PaperAmounts [ p ];
               b := b + previousPapers [ p ] * PaperAmounts [ p ];
            end;

            if ( amount <> a ) or ( not isGood )
            then
               if r = ReviseEKM
               then FirstLine := WriteOneCashTransaction( span , i , a , b , FirstLine, r );
            if ( amount = a ) and isGood and ( atId = 9 )
            then
               if r = ReviseGood
               then FirstLine := WriteOneCashTransaction( span , i , a , b , FirstLine, r );
         end;
   if StringList.Count > 0 then StringList.SaveToFile( filename );
   StringList.Clear;
end;{1. Сверка наличных операций}

procedure ReviseShiftsAndEncashments ( span: integer );
begin
   StringList.Clear;
   ParsingList.Clear;
   FileRecords.Clear;
   Transactions.Clear;
   Shifts.Clear;
   try
   {Читаем файл}
//   writeLn('Читаю файлик');
   ReadAction( 'shifts.csv' );

{ 2. Сверка инкассаций:
   для инкассаций дополнительно сравниванием сумму купюр инкассации с суммой купюр предшествующих платежей
   + список хороших инкассаций
   - список плохих операций

  1. Сверка наличных операций:
   для каждой наличной операции сравниваем сумму операции и сумму купюр
 + хорошие операции пропускаем
 - плохие операции выводим на экран}
//   writeLn('Сверяю инкассации');
   ReviseCashTransactions ( span , ReviseGood , 'Good encashments.csv' );
   WriteToGlobalLog ('Проведена сверка наличных операций');
   ReviseCashTransactions ( span , ReviseEKM  , 'Bad cash transactions.csv' );
   WriteToGlobalLog ('Проведена сверка инкассаций');

   {3. Сверка итогов:
   разность нарастающих итогов ККМ = сумма всех платежей
   разность итогов ЕКМ = сумма наличных платежей и движений средств
   сумма ЕКМ на начало смены = сумма ЕКМ на конец предыдущей смены
   сумма ККМ на начало смены = сумма ККМ на конец предыдущей смены
   + хорошие смены пропускаем
   - плохие смены выводим на экран}
//   writeLn('сверяю итоги');
   ReviseShifts ( span , ReviseGood , 'Good shifts.csv'        );
   ReviseShifts ( span , ReviseEKM  , 'Bad shifts.csv'         );
   ReviseShifts ( span , ReviseKKM  , 'KKM strange shifts.csv' );
   WriteToGlobalLog ('Проведена сверка итогов');
   except
      on E: Exception do  begin
         StringList.Clear;
         StringList.Add( E.ClassName + ': ' + E.Message );
         if StringList.Count > 0 then StringList.SaveToFile('Shift revise failed.txt');
      end;
   end;
end;

begin
   { ---===<*> Инициализация <*>===--- }

   FileRecords        := TList<TFileRecord>.Create;
   Transactions       := TList<TTransaction>.Create;
   Shifts             := TList<TShift>.Create;
   StringList         := TStringList.Create;
   ParsingList        := TStringList.Create;

   FileRecordsTimeComparer := TDelegatedComparer<TFileRecord>.Create(
      function( const item1, item2: TFileRecord ): Integer
         begin
            Result := CompareDateTime( TFileRecord ( item1 ).actDt , TFileRecord ( item2 ).actDt );
         end);

   TransactionsTimeComparer := TDelegatedComparer<TTransaction>.Create(
       function( const item1, item2: TTransaction ): Integer
         begin
            Result := CompareDateTime ( TTransaction( item1 ).Dt, TTransaction( item2 ).Dt );
         end);

   ShiftsTimeComparer := TDelegatedComparer<TShift>.Create(
       function( const item1, item2: TShift ): Integer
         begin
            Result := CompareDateTime ( TShift( item1 ).FromDt, TShift( item2 ).FromDt );
         end);

   PaperAmounts [ p10   ] := 1000;
   PaperAmounts [ p50   ] := 5000;
   PaperAmounts [ p100  ] := 10000;
   PaperAmounts [ p200  ] := 20000;
   PaperAmounts [ p500  ] := 50000;
   PaperAmounts [ p1000 ] := 100000;
   PaperAmounts [ p2000 ] := 200000;
   PaperAmounts [ p5000 ] := 500000;

   PaymentType [ NoPayment ] := 'ошибка';
   PaymentType [ CASH      ] := 'наличными';
   PaymentType [ BANKCARD  ] := 'по карте';

end.
